package top.cardone.data.jdbc.dao.impl;

import com.google.common.collect.*;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SingleColumnRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.context.util.TableUtils;
import top.cardone.core.util.action.Action1;
import top.cardone.core.util.func.Func0;
import top.cardone.core.util.func.Func1;
import top.cardone.core.util.func.Func2;
import top.cardone.data.jdbc.support.NamedParameterJdbcOperationsSupport;

import java.sql.ResultSet;
import java.util.List;
import java.util.Map;

/**
 * @author yao hai tao
 * @date 2015/8/26
 */
@Log4j2
public class CrudDaoImpl implements top.cardone.data.dao.CrudDao {
    @Getter
    private Table<String, String, Object> configTable = HashBasedTable.create();

    @Override
    public int delete(Object delete) {
        Map<String, Object> deleteMap = this.toMap(delete, "delete");

        Map<String, Object> newDeleteMap = Maps.newHashMap();

        for (Map.Entry<String, Object> deleteEntry : deleteMap.entrySet()) {
            newDeleteMap.put("where_and_eq_" + deleteEntry.getKey(), Boolean.TRUE.toString());
            newDeleteMap.put("where_and_eq_" + deleteEntry.getKey() + "_value", deleteEntry.getValue());
        }

        return this.updateBySqlFileName("delete", newDeleteMap);
    }

    @Override
    public int deleteByFunc(@NonNull Func2 func, Object delete) {
        return (int) func.func(this, delete);
    }

    @Override
    public int deleteByFuncId(@NonNull String funcId, Object delete) {
        return this.deleteByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), delete);
    }

    @Override
    public int deleteBySqlFileName(String sqlFileName, Object delete) {
        return this.update(this.getSqlFilePath(sqlFileName), this.toMap(delete, "delete"));
    }

    @Override
    public int deleteAll() {
        Map<String, Object> newDeleteMap = Maps.newHashMap();

        newDeleteMap.put("delete_all", Boolean.TRUE.toString());

        return this.updateBySqlFileName("delete", newDeleteMap);
    }

    @Override
    public int[] deleteListBySqlFileName(String sqlFileName, List<Object> deleteList) {
        if (CollectionUtils.isEmpty(deleteList)) {
            return new int[0];
        }

        List<Map<String, Object>> newDeleteListMap = Lists.newArrayList();

        for (Object delete : deleteList) {
            newDeleteListMap.add(this.toMap(delete, "delete"));
        }

        return this.updateListBySqlFileName(sqlFileName, newDeleteListMap);
    }

    @Override
    public int[] deleteList(List<Object> deleteList) {
        if (CollectionUtils.isEmpty(deleteList)) {
            return new int[0];
        }

        List<Map<String, Object>> newDeleteListMap = Lists.newArrayList();

        for (Object delete : deleteList) {
            Map<String, Object> deleteMap = this.toMap(delete, "delete");

            Map<String, Object> newDeleteMap = Maps.newHashMap();

            for (Map.Entry<String, Object> deleteEntry : deleteMap.entrySet()) {
                newDeleteMap.put("where_and_eq_" + deleteEntry.getKey(), Boolean.TRUE.toString());
                newDeleteMap.put("where_and_eq_" + deleteEntry.getKey() + "_value", deleteEntry.getValue());
            }

            newDeleteListMap.add(newDeleteMap);
        }

        return this.updateListBySqlFileName("delete", newDeleteListMap);
    }

    @Override
    public int[] deleteListByFunc(@NonNull Func2 func, List<Object> deleteList) {
        return (int[]) func.func(this, deleteList);
    }

    @Override
    public int[] deleteListByFuncId(@NonNull String funcId, List<Object> deleteList) {
        return this.deleteListByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), deleteList);
    }

    @Override
    public List<Map<String, Object>> findList(Object findList) {
        Map<String, Object> findListMap = this.toMap(findList, "find");

        Map<String, Object> newFindListMap = Maps.newHashMap();

        for (Map.Entry<String, Object> findListEntry : findListMap.entrySet()) {
            newFindListMap.put("where_and_eq_" + findListEntry.getKey(), Boolean.TRUE.toString());
            newFindListMap.put("where_and_eq_" + findListEntry.getKey() + "_value", findListEntry.getValue());
        }

        newFindListMap.putAll(findListMap);

        return this.findListBySqlFileName("find", newFindListMap);
    }

    @Override
    public List<Map<String, Object>> findListByFunc(@NonNull Func2 func, Object findList) {
        return (List<Map<String, Object>>) func.func(this, findList);
    }

    @Override
    public List<Map<String, Object>> findListByFuncId(@NonNull String funcId, Object findList) {
        return this.findListByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), findList);
    }

    @Override
    public List<Map<String, Object>> findList(@NonNull String sqlFilePath) {
        return this.findList(sqlFilePath, null);
    }

    @Override
    public List<Map<String, Object>> findList(@NonNull String sqlFilePath, Map<String, ?> findList) {
        String parseBeanId = this.getParseBeanId();
        String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

        return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, findList, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.queryForList(sql, findList));
    }

    @Override
    public List<Map<String, Object>> findListBySqlFileName(String sqlFileName, Object findList) {
        return this.findList(this.getSqlFilePath(sqlFileName), this.toMap(findList, "find"));
    }

    @Override
    public Map<String, Object> findOne(Object findOne) {
        Map<String, Object> findOneMap = this.toMap(findOne, "find");

        Map<String, Object> newFindOneMap = Maps.newHashMap();

        for (Map.Entry<String, Object> findOneEntry : findOneMap.entrySet()) {
            newFindOneMap.put("where_and_eq_" + findOneEntry.getKey(), Boolean.TRUE.toString());
            newFindOneMap.put("where_and_eq_" + findOneEntry.getKey() + "_value", findOneEntry.getValue());
        }

        return this.findOneBySqlFileName("find", newFindOneMap);
    }

    @Override
    public Map<String, Object> findOneByFunc(@NonNull Func2 func, Object findOne) {
        return (Map<String, Object>) func.func(this, findOne);
    }

    @Override
    public Map<String, Object> findOneByFuncId(@NonNull String funcId, Object findOne) {
        return this.findOneByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), findOne);
    }

    @Override
    public Map<String, Object> findOne(@NonNull String sqlFilePath, Map<String, ?> findOne) {
        List<Map<String, Object>> findList = this.findList(sqlFilePath, findOne);

        if (CollectionUtils.isEmpty(findList)) {
            return null;
        }

        if (findList.size() > 1) {
            log.error("单条数据查询方法,查询出" + findList.size() + "条数据:sqlFilePath=" + sqlFilePath + ", findOne=" + findOne);
        }

        return findList.get(0);
    }

    @Override
    public Map<String, Object> findOneBySqlFileName(String sqlFileName, Object findOne) {
        return this.findOne(this.getSqlFilePath(sqlFileName), this.toMap(findOne, "find"));
    }

    @Override
    public String[] readListCodeForConfigTable(Map<String, Object> objectMap) {
        String codesString = MapUtils.getString(objectMap, "cardone_where_codes", (String) configTable.get("config", "codes"));

        return StringUtils.split(StringUtils.remove(codesString, StringUtils.SPACE), ",");
    }

    @Override
    public String getNamedParameterJdbcOperationsBeanId() {
        return (String) configTable.get("config", "namedParameterJdbcOperationsBeanId");
    }

    @Override
    public String getParseBeanId() {
        return (String) configTable.get("config", "parseBeanId");
    }

    @Override
    public String[] readListPkForConfigTable(Map<String, Object> objectMap) {
        String pksString = MapUtils.getString(objectMap, "cardone_where_pks", (String) configTable.get("config", "pks"));

        String[] pks = StringUtils.split(StringUtils.remove(pksString, StringUtils.SPACE), ",");

        if (ArrayUtils.isNotEmpty(pks)) {
            return pks;
        }

        return this.readListCodeForConfigTable(objectMap);
    }

    @Override
    public String[] readListWhereForConfigTable(Map<String, Object> objectMap) {
        String[] pks = this.readListPkForConfigTable(objectMap);

        String[] codes = this.readListCodeForConfigTable(objectMap);

        if (ArrayUtils.isEmpty(codes)) {
            return pks;
        }

        for (String pk : pks) {
            Object val = objectMap.get(pk);

            if (val == null) {
                return codes;
            }

            if (val instanceof String && StringUtils.isBlank(val.toString())) {
                return codes;
            }
        }

        return pks;
    }

    public String getSqlFilePath(@NonNull String sqlFilePath) {
        String sqlFileSuffix = StringUtils.defaultIfBlank((String) configTable.get("config", "sqlFileSuffix"), ".ftl");

        if (StringUtils.startsWith(sqlFilePath, "/")) {
            return sqlFilePath + sqlFileSuffix;
        }

        String sqlFileRoot = (String) configTable.get("config", "sqlFileRoot");

        return sqlFileRoot + sqlFilePath + sqlFileSuffix;
    }

    @Override
    public int insert(Object insert) {
        Map<String, Object> insertMap = this.toMap(insert, "insert");

        Map<String, Object> newInsertMap = Maps.newHashMap();

        for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
            newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
            newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
        }

        return this.updateBySqlFileName("insert", newInsertMap);
    }

    @Override
    public int insertByFunc(@NonNull Func2 func, Object insert) {
        return (int) func.func(this, insert);
    }

    @Override
    public int insertByFuncId(@NonNull String funcId, Object insert) {
        return this.insertByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), insert);
    }

    @Override
    public int insertByNotExists(Object insert) {
        Map<String, Object> insertMap = top.cardone.context.util.MapUtils.toMap(insert);

        String[] wheres = this.readListWhereForConfigTable(insertMap);

        insertMap = this.toMap(insertMap, "insert");

        Map<String, Object> newInsertMap = Maps.newHashMap();

        for (String where : wheres) {
            newInsertMap.put("where_and_eq_" + where, Boolean.TRUE.toString());
            newInsertMap.put("where_and_eq_" + where + "_value", insertMap.get(where));
        }

        for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
            newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
            newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
        }

        return this.updateBySqlFileName("insertByNotExists", newInsertMap);
    }

    @Override
    public int[] insertList(List<Object> insertList) {
        if (CollectionUtils.isEmpty(insertList)) {
            return new int[0];
        }

        List<Map<String, Object>> newInsertMapList = Lists.newArrayList();

        for (Object insert : insertList) {
            Map<String, Object> insertMap = this.toMap(insert, "insert");

            Map<String, Object> newInsertMap = Maps.newHashMap();

            for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
                newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
                newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
            }

            newInsertMapList.add(newInsertMap);
        }

        return this.updateListBySqlFileName("insert", newInsertMapList);
    }

    @Override
    public int insertOnConflict(Object insert) {
        Map<String, Object> insertMap = top.cardone.context.util.MapUtils.toMap(insert);

        String[] wheres = this.readListWhereForConfigTable(insertMap);

        insertMap = this.toMap(insertMap, "insert");

        Map<String, Object> newInsertMap = Maps.newHashMap();

        for (String where : wheres) {
            newInsertMap.put("index_" + where, Boolean.TRUE.toString());
            newInsertMap.put("index_" + where + "_value", insertMap.get(where));
        }

        for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
            newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
            newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
        }

        return this.updateBySqlFileName("insertOnConflict", newInsertMap);
    }

    @Override
    public int executeQueryBySqlFileName(String sqlFileName, Object param, Action1<Map<String, Object>> action) throws DataAccessException {
        RowMapper<Map<String, Object>> rowMapper = new ColumnMapRowMapper();

        return this.executeQuery(this.getSqlFilePath(sqlFileName), param, rowMapper, action);
    }

    @Override
    public <T> int executeQueryBySqlFileName(String sqlFileName, Object param, Class<T> elementType, Action1<T> action) throws DataAccessException {
        RowMapper<T> rowMapper = new SingleColumnRowMapper<>();

        return this.executeQuery(this.getSqlFilePath(sqlFileName), param, rowMapper, action);
    }

    @Override
    public int executeQueryByFunc(Func2 func, Object param) {
        return (int) func.func(this, param);
    }

    @Override
    public int executeQueryByFuncId(String funcId, Object param) {
        return this.executeQueryByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), param);
    }

    protected <T> int executeQuery(String sqlFilePath, Object param, RowMapper<T> rowMapper, Action1<T> action) throws DataAccessException {
        String parseBeanId = this.getParseBeanId();
        String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

        Map<String, Object> paramMap = this.toMap(param, "executeQuery");

        return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).
                func(parseBeanId, sqlFilePath, paramMap, namedParameterJdbcOperationsBeanId,
                        (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.execute(sql, paramMap,
                                ps -> {
                                    ResultSet rs = null;

                                    try {
                                        rs = ps.executeQuery();

                                        int rowNum = 0;

                                        while (rs.next()) {
                                            T t = rowMapper.mapRow(rs, rowNum++);

                                            action.action(t);
                                        }

                                        return rowNum;
                                    } finally {
                                        JdbcUtils.closeResultSet(rs);
                                    }
                                }));
    }

    @Override
    public int saveOnConflict(Object save) {
        Map<String, Object> saveMap = top.cardone.context.util.MapUtils.toMap(save);

        String[] wheres = this.readListWhereForConfigTable(saveMap);

        Map<String, Object> newSaveMap = Maps.newHashMap();

        Map<String, Object> insertMap = this.toMap(saveMap, "insert");

        for (String where : wheres) {
            newSaveMap.put("index_" + where, Boolean.TRUE.toString());
            newSaveMap.put("index_" + where + "_value", insertMap.get(where));
        }

        for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
            newSaveMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
            newSaveMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
        }

        Map<String, Object> updateMap = this.toMap(save, "update");

        for (Map.Entry<String, Object> updateEntry : updateMap.entrySet()) {
            if (ArrayUtils.contains(wheres, updateEntry.getKey())) {
                continue;
            }

            newSaveMap.put("update_" + updateEntry.getKey(), Boolean.TRUE.toString());
            newSaveMap.put("update_" + updateEntry.getKey() + "_value", updateEntry.getValue());
        }

        return this.updateBySqlFileName("saveOnConflict", newSaveMap);
    }

    @Override
    public int[] insertListByFunc(@NonNull Func2 func, List<Object> insertList) {
        return (int[]) func.func(this, insertList);
    }

    @Override
    public int[] insertListByFuncId(@NonNull String funcId, List<Object> insertList) {
        return this.insertListByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), insertList);
    }

    @Override
    public int[] insertListByNotExists(List<Object> insertList) {
        if (CollectionUtils.isEmpty(insertList)) {
            return new int[0];
        }

        List<Map<String, Object>> newInsertListMap = Lists.newArrayList();

        for (Object insert : insertList) {
            Map<String, Object> insertMap = top.cardone.context.util.MapUtils.toMap(insert);

            String[] wheres = this.readListWhereForConfigTable(insertMap);

            insertMap = this.toMap(insertMap, "insert");

            Map<String, Object> newInsertMap = Maps.newHashMap();

            for (String where : wheres) {
                newInsertMap.put("where_and_eq_" + where, Boolean.TRUE.toString());
                newInsertMap.put("where_and_eq_" + where + "_value", insertMap.get(where));
            }

            for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
                newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
                newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
            }

            newInsertListMap.add(newInsertMap);
        }

        return this.updateListBySqlFileName("insertByNotExists", newInsertListMap);
    }

    @Override
    public int[] insertListOnConflict(List<Object> insertList) {
        if (CollectionUtils.isEmpty(insertList)) {
            return new int[0];
        }

        List<Map<String, Object>> newInsertListMap = Lists.newArrayList();

        for (Object insert : insertList) {
            Map<String, Object> insertMap = top.cardone.context.util.MapUtils.toMap(insert);

            String[] wheres = this.readListWhereForConfigTable(insertMap);

            insertMap = this.toMap(insertMap, "insert");

            Map<String, Object> newInsertMap = Maps.newHashMap();

            for (String where : wheres) {
                newInsertMap.put("index_" + where, Boolean.TRUE.toString());
                newInsertMap.put("index_" + where + "_value", insertMap.get(where));
            }

            for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
                newInsertMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
                newInsertMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
            }

            newInsertListMap.add(newInsertMap);
        }

        return this.updateListBySqlFileName("insertOnConflict", newInsertListMap);
    }

    @Override
    public List<Object> readList(Object readList) {
        Map<String, Object> readListMap = this.toMap(readList, "read");

        Map<String, Object> newReadListMap = Maps.newHashMap();

        for (Map.Entry<String, Object> readListEntry : readListMap.entrySet()) {
            newReadListMap.put("where_and_eq_" + readListEntry.getKey(), Boolean.TRUE.toString());
            newReadListMap.put("where_and_eq_" + readListEntry.getKey() + "_value", readListEntry.getValue());
        }

        newReadListMap.put("object_id", readListMap.get("object_id"));

        return this.readListBySqlFileName("read", newReadListMap);
    }

    @Override
    public List<Object> readListByFunc(@NonNull Func2 func, Object readList) {
        return (List<Object>) func.func(this, readList);
    }

    @Override
    public List<Object> readListByFuncId(@NonNull String funcId, Object readList) {
        return this.readListByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), readList);
    }

    @Override
    public List<Object> readList(@NonNull String sqlFilePath, Map<String, ?> readList) {
        String parseBeanId = this.getParseBeanId();
        String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

        return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath,
                readList, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) ->
                        namedParameterJdbcOperations.queryForList(sql, readList, Object.class));
    }

    @Override
    public List<Object> readListBySqlFileName(String sqlFileName, Object readList) {
        return this.readList(this.getSqlFilePath(sqlFileName), this.toMap(readList, "read"));
    }

    @Override
    public Object readOne(Object readOne) {
        Map<String, Object> readOneMap = this.toMap(readOne, "read");

        Map<String, Object> newReadOneMap = Maps.newHashMap();

        for (Map.Entry<String, Object> readOneEntry : readOneMap.entrySet()) {
            if ("object_id".equals(readOneEntry.getKey())) {
                continue;
            }

            newReadOneMap.put("where_and_eq_" + readOneEntry.getKey(), Boolean.TRUE.toString());
            newReadOneMap.put("where_and_eq_" + readOneEntry.getKey() + "_value", readOneEntry.getValue());
        }

        newReadOneMap.put("object_id", readOneMap.get("object_id"));

        return this.readOneBySqlFileName("read", newReadOneMap);
    }

    @Override
    public Object readOneByFunc(@NonNull Func2 func, Object readOne) {
        return func.func(this, readOne);
    }

    @Override
    public Object readOneByFuncId(@NonNull String funcId, Object readOne) {
        return this.readOneByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), readOne);
    }

    @Override
    public Object readOne(@NonNull String sqlFilePath, Map<String, ?> readOne) {
        List<Object> readList = this.readList(sqlFilePath, readOne);

        if (CollectionUtils.isEmpty(readList)) {
            return null;
        }

        if (readList.size() > 1) {
            log.error(StringUtils.join("单条数据查询方法,查询出", readList.size(), "条数据;\r\nsqlFilePath:", sqlFilePath + "\r\nreadOne:", readOne));
        }

        return readList.get(0);
    }

    @Override
    public Object readOneBySqlFileName(String sqlFileName, Object readOne) {
        return this.readOne(this.getSqlFilePath(sqlFileName), this.toMap(readOne, "read"));
    }

    @Override
    public int save(Object save) {
        return this.save(null, null, save);
    }

    @Override
    public int saveByFunc(@NonNull Func2 func, Object save) {
        return (int) func.func(this, save);
    }

    @Override
    public int saveByFuncId(@NonNull String funcId, Object save) {
        return this.saveByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), save);
    }

    @Override
    public int save(String updateSqlFilePath, String insertSqlFilePath, Object save) {
        int updateCount;

        if (StringUtils.isBlank(updateSqlFilePath)) {
            updateCount = this.update(save);
        } else {
            Map<String, Object> updateMap = this.toMap(save, "update");

            updateCount = this.update(updateSqlFilePath, updateMap);
        }

        if (updateCount > 0) {
            if (updateCount > 1) {
                log.error(StringUtils.join("更新超过1条:", updateCount, "条数据;\r\nupdate:", save));
            }

            return updateCount;
        }

        if (StringUtils.isBlank(insertSqlFilePath)) {
            return this.insert(save);
        } else {
            Map<String, Object> insertMap = this.toMap(save, "insert");

            return this.update(insertSqlFilePath, insertMap);
        }
    }

    @Override
    public int saveBySqlFileName(String updateSqlFileName, String insertSqlFileName, Object save) {
        return this.save(this.getSqlFilePath(updateSqlFileName), this.getSqlFilePath(insertSqlFileName), save);
    }

    public void setConfigTable(Map<String, Map<String, Object>> configTable) {
        this.configTable = TableUtils.convert(configTable);
    }

    public Map<String, Object> toMap(Object obj, String key) {
        Map<String, Object> map = top.cardone.context.util.MapUtils.toMap(obj, key);

        return toMap(map, key);
    }

    public Map<String, Object> toMap(Map<String, Object> map, String key) {
        if ("update".equals(key) && MapUtils.getBoolean(map, "skipFillFields", false)) {
            return map;
        }

        this.toMapForConfig(map, key);

        String appendConfigValueKey = MapUtils.getString(map, "appendConfigValueKey");

        if (StringUtils.isNotBlank(appendConfigValueKey) && !StringUtils.equals(appendConfigValueKey, key)) {
            this.toMapForConfig(map, appendConfigValueKey);
        }

        return map;
    }

    private void toMapForConfig(Map<String, Object> map, String key) {
        Map<String, Object> row = configTable.row(StringUtils.defaultIfBlank(key, "update"));

        if (MapUtils.isEmpty(row)) {
            return;
        }

        for (Map.Entry<String, Object> rowEntry : row.entrySet()) {
            if (map.containsKey(rowEntry.getKey())) {
                continue;
            }

            toMapRowEntry(map, rowEntry);
        }
    }

    private void toMapRowEntry(Map<String, Object> newMap, Map.Entry<String, Object> rowEntry) {
        Object func;

        if (rowEntry.getValue() instanceof String) {
            func = ApplicationContextHolder.getBean((String) rowEntry.getValue());
        } else {
            func = rowEntry.getValue();
        }

        if (func == null) {
            return;
        }

        if (func instanceof Func0) {
            newMap.put(rowEntry.getKey(), ((Func0) func).func());
        } else if (func instanceof Func1) {
            newMap.put(rowEntry.getKey(), ((Func1) func).func(newMap));
        }
    }

    @Override
    public int update(Object update) {
        Map<String, Object> updateMap = top.cardone.context.util.MapUtils.toMap(update);

        String[] wheres = this.readListWhereForConfigTable(updateMap);

        updateMap = this.toMap(updateMap, "update");

        Map<String, Object> newUpdateMap = Maps.newHashMap();

        for (String where : wheres) {
            newUpdateMap.put("where_and_eq_" + where, Boolean.TRUE.toString());
            newUpdateMap.put("where_and_eq_" + where + "_value", updateMap.get(where));
        }

        for (Map.Entry<String, Object> updateEntry : updateMap.entrySet()) {
            if (ArrayUtils.contains(wheres, updateEntry.getKey())) {
                continue;
            }

            newUpdateMap.put("update_" + updateEntry.getKey(), Boolean.TRUE.toString());
            newUpdateMap.put("update_" + updateEntry.getKey() + "_value", updateEntry.getValue());
        }

        return this.updateBySqlFileName("update", newUpdateMap);
    }

    @Override
    public int updateByFunc(@NonNull Func2 func, Object update) {
        return (int) func.func(this, update);
    }

    @Override
    public int updateByFuncId(@NonNull String funcId, Object update) {
        return this.updateByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), update);
    }

    @Override
    public int update(@NonNull String sqlFilePath, Map<String, ?> update) {
        String parseBeanId = this.getParseBeanId();
        String namedParameterJdbcOperationsBeanId = this.getNamedParameterJdbcOperationsBeanId();

        return ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class).func(parseBeanId, sqlFilePath, update, namedParameterJdbcOperationsBeanId, (sql, namedParameterJdbcOperations) -> namedParameterJdbcOperations.update(sql, update));
    }

    @Override
    public int updateBySqlFileName(String sqlFileName, Object update) {
        return this.update(this.getSqlFilePath(sqlFileName), this.toMap(update, "update"));
    }

    @Override
    public int[] updateList(List<Object> updateList) {
        if (CollectionUtils.isEmpty(updateList)) {
            return new int[0];
        }

        List<Map<String, Object>> newUpdateListMap = Lists.newArrayList();

        for (Object update : updateList) {
            Map<String, Object> updateMap = top.cardone.context.util.MapUtils.toMap(update);

            String[] wheres = this.readListWhereForConfigTable(updateMap);

            updateMap = this.toMap(updateMap, "update");

            Map<String, Object> newUpdateMap = Maps.newHashMap();

            for (String where : wheres) {
                newUpdateMap.put("where_and_eq_" + where, Boolean.TRUE.toString());
                newUpdateMap.put("where_and_eq_" + where + "_value", updateMap.get(where));
            }

            for (Map.Entry<String, Object> updateEntry : updateMap.entrySet()) {
                if (ArrayUtils.contains(wheres, updateEntry.getKey())) {
                    continue;
                }

                newUpdateMap.put("update_" + updateEntry.getKey(), Boolean.TRUE.toString());
                newUpdateMap.put("update_" + updateEntry.getKey() + "_value", updateEntry.getValue());
            }

            newUpdateListMap.add(newUpdateMap);
        }

        return this.updateListBySqlFileName("update", newUpdateListMap);
    }

    @Override
    public int[] updateListByFunc(@NonNull Func2 func, List<Object> updateList) {
        return (int[]) func.func(this, updateList);
    }

    @Override
    public int[] updateListByFuncId(@NonNull String funcId, List<Object> updateList) {
        return this.updateListByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), updateList);
    }

    @Override
    public int[] saveList(List<Object> saveList) {
        if (CollectionUtils.isEmpty(saveList)) {
            return new int[0];
        }

        int[] saveCounts = new int[saveList.size()];

        for (int i = 0; i < saveList.size(); i++) {
            saveCounts[i] = this.save(saveList.get(i));
        }

        return saveCounts;
    }

    @Override
    public int[] saveListOnConflict(List<Object> saveList) {
        if (CollectionUtils.isEmpty(saveList)) {
            return new int[0];
        }

        List<Map<String, Object>> saveListMap = Lists.newArrayList();

        for (Object save : saveList) {
            Map<String, Object> saveMap = top.cardone.context.util.MapUtils.toMap(save);

            String[] wheres = this.readListWhereForConfigTable(saveMap);

            Map<String, Object> newSaveMap = Maps.newHashMap();

            Map<String, Object> insertMap = this.toMap(saveMap, "insert");

            for (String where : wheres) {
                newSaveMap.put("index_" + where, Boolean.TRUE.toString());
                newSaveMap.put("index_" + where + "_value", insertMap.get(where));
            }

            for (Map.Entry<String, Object> insertEntry : insertMap.entrySet()) {
                newSaveMap.put("insert_" + insertEntry.getKey(), Boolean.TRUE.toString());
                newSaveMap.put("insert_" + insertEntry.getKey() + "_value", insertEntry.getValue());
            }

            Map<String, Object> updateMap = this.toMap(save, "update");

            for (Map.Entry<String, Object> updateEntry : updateMap.entrySet()) {
                if (ArrayUtils.contains(wheres, updateEntry.getKey())) {
                    continue;
                }

                newSaveMap.put("update_" + updateEntry.getKey(), Boolean.TRUE.toString());
                newSaveMap.put("update_" + updateEntry.getKey() + "_value", updateEntry.getValue());
            }

            saveListMap.add(newSaveMap);
        }

        return this.updateListBySqlFileName("saveOnConflict", saveListMap);
    }

    @Override
    public int[] saveListByFunc(@NonNull Func2 func, List<Object> saveList) {
        return (int[]) func.func(this, saveList);
    }

    @Override
    public int[] saveListByFuncId(@NonNull String funcId, List<Object> saveList) {
        return this.saveListByFunc(ApplicationContextHolder.getBean(Func2.class, funcId), saveList);
    }

    @Override
    public int[] updateList(@NonNull String sqlFilePath, List<Map<String, Object>> updateList) {
        ArrayListMultimap<String, Map<String, Object>> updateListMap = ArrayListMultimap.create();

        for (Map<String, Object> update : updateList) {
            String sql = ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class)
                    .parseSql(this.getParseBeanId(), sqlFilePath, update);

            updateListMap.put(sql, update);
        }

        int[] updateListMapCounts = null;

        for (String updateListMapKey : updateListMap.keySet()) {
            List<Map<String, Object>> itemUpdateList = updateListMap.get(updateListMapKey);

            final SqlParameterSource[] batchArgs = new SqlParameterSource[itemUpdateList.size()];

            for (int i = 0; i < itemUpdateList.size(); i++) {
                batchArgs[i] = new MapSqlParameterSource(itemUpdateList.get(i));
            }

            int[] updateCounts = ApplicationContextHolder.getBean(NamedParameterJdbcOperationsSupport.class)
                    .getNamedParameterJdbcOperations(this.getNamedParameterJdbcOperationsBeanId())
                    .batchUpdate(updateListMapKey, batchArgs);

            updateListMapCounts = ArrayUtils.addAll(updateListMapCounts, updateCounts);
        }

        return updateListMapCounts;
    }

    @Override
    public int[] updateListBySqlFileName(String sqlFileName, List<Map<String, Object>> updateList) {
        return this.updateList(this.getSqlFilePath(sqlFileName), updateList);
    }

    @Transactional
    public void execute(String sql) {
        ApplicationContextHolder.getBean(org.springframework.jdbc.core.JdbcOperations.class).execute(sql);
    }

    @Override
    @Transactional
    public int[] batchUpdate(String... sql) {
        return ApplicationContextHolder.getBean(org.springframework.jdbc.core.JdbcOperations.class).batchUpdate(sql);
    }
}
